package com.yzy.mapper;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.yzy.domain.entity.annotation.Column;
import com.yzy.domain.entity.annotation.EnumString;
import com.yzy.domain.entity.annotation.Id;
import com.yzy.domain.entity.annotation.Index;
import com.yzy.domain.entity.system.BaseEntity;
import com.yzy.mapper.system.TableDao;
import com.yzy.util.ClassLoadUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.beans.Transient;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @Description: mybatis自动生成表、更新新字段到表中
 */
@Component
public class TableUtils {

    @Resource
    private TableDao tableDao;

    /**
     * 数据库名称
     */
    private static String DATA_BASE_NAME;

    /**
     * 监听路径 -> PO包
     */
    private static String LISTEN_LOCATION;

    /**
     * 是否开启新字段自动更新到表中机制
     */
    private static Boolean IS_OPEN_CHECK_COLUMN;

    /**
     * @param environment: Spring环境
     * @Description: 与GlobalProperties解耦，可以单独提取出来做为工具到其他项目
     */
    public TableUtils(Environment environment) {
        // 监听路径
        TableUtils.LISTEN_LOCATION = environment.getProperty("spring.datasource.ddl.listen-location");
        // 数据库名称
        String[] urlArray = Objects.requireNonNull(environment.getProperty("spring.datasource.url")).split("/");
        TableUtils.DATA_BASE_NAME = urlArray[3];
        if (urlArray[3].contains("?")) {
            TableUtils.DATA_BASE_NAME = urlArray[3].substring(0, urlArray[3].indexOf("?"));
        }
        // 是否开启更新机制
        try {
            TableUtils.IS_OPEN_CHECK_COLUMN = Boolean.valueOf(Objects.requireNonNull(environment.getProperty("spring.datasource.ddl.open-column-check")).trim());
        } catch (Exception e) {
            TableUtils.IS_OPEN_CHECK_COLUMN = true;
        }
    }

    /**
     * @Description: 数据库表自动化创建
     * servlet init 启动执行（构造执行之后）
     */
    @PostConstruct
    public void initDynamicCheckTable() throws ClassNotFoundException, IOException {
        List<Class<?>> packageClasses = ClassLoadUtils.getPackageClassesByResource(LISTEN_LOCATION);
        for (Class<?> clazz : packageClasses) {
            this.createTable(clazz);
        }
    }

    /**
     * @param clazz: 对应PO
     * @Description: 根据指定的class创建表
     */
    private void createTable(Class<?> clazz) {
        if (clazz.isEnum() || clazz == BaseEntity.class) {
            return;
        }
        // 表名转驼峰
        String tableName = StringUtils.camelToUnderline(StringUtils.firstToLowerCase(clazz.getSimpleName()));
        TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
        if (Objects.nonNull(tableNameAnnotation) && org.apache.commons.lang3.StringUtils.isNotBlank(tableNameAnnotation.value())) {
            tableName = tableNameAnnotation.value();
        }
        // 检查表是否存在
        List<String> tableList = tableDao.getTablesByDataBaseAndTable(DATA_BASE_NAME, tableName);
        List<String> columnList = tableDao.getColumnsByDataBaseAndTable(DATA_BASE_NAME, tableName);
        if (!IS_OPEN_CHECK_COLUMN && CollectionUtils.isNotEmpty(tableList)) {
            return;
        }
        // 反射获取类中所有字段
        List<Field> fieldArray = this.getClassFields(clazz);
        // 装载持久化map
        Map<String, String> columns = new LinkedHashMap<>();
        for (Field field : fieldArray) {
            this.fetchColumnByFiled(tableName, tableList, columnList, columns, field);
        }
        if (CollectionUtils.isEmpty(columns) || CollectionUtils.isNotEmpty(tableList)) {
            return;
        }
        // 创建新表
        tableDao.createTable(tableName, columns);
    }

    /**
     * @param tableName:  表名
     * @param tableList:  数据库已存在的表集合
     * @param columnList: 数据库当前表已存在的列集合
     * @param columns:    PO对应数据库字段、类型MAP
     * @param field:      当前字段
     * @Description: 根据指定的field获取数据库列
     */
    private void fetchColumnByFiled(String tableName, List<String> tableList, List<String> columnList, Map<String, String> columns, Field field) {
        field.setAccessible(true);
        // 忽略静态字段
        if (Modifier.isStatic(field.getModifiers())) {
            return;
        }
        // 忽略指定字段
        if (Objects.nonNull(field.getAnnotation(TableField.class))) {
            TableField tableField = field.getAnnotation(TableField.class);
            if (!tableField.exist()) {
                return;
            }
        }
        if (Objects.nonNull(field.getAnnotation(Transient.class))) {
            Transient transientField = field.getAnnotation(Transient.class);
            if (Objects.nonNull(transientField)) {
                return;
            }
        }
        String fieldName = this.getColumnMapByField(columns, field, tableName);
        if (CollectionUtils.isNotEmpty(tableList) && !columnList.contains(fieldName)) {
            // 添加新字段
            tableDao.addColumnOfTable(tableName, fieldName, columns.get(fieldName));
        }
    }

    /**
     * @param columns: PO对应数据库字段、类型MAP
     * @param field:   当前字段
     * @return 数据库对应列名
     * @Description: 添加指定字段对应的持久化类型到map
     */
    private String getColumnMapByField(Map<String, String> columns, Field field, String tableName) {
        // 字段名转驼峰
        String fieldName = StringUtils.camelToUnderline(StringUtils.firstToLowerCase(field.getName()));
        TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
        if (Objects.nonNull(tableFieldAnnotation) && org.apache.commons.lang3.StringUtils.isNotBlank(tableFieldAnnotation.value())) {
            fieldName = tableFieldAnnotation.value();
        }

        // 如有新的类型在switch中加入相应的处理
        switch (field.getType().getSimpleName()) {
            case "String":
                String indexString = "";
                Index index = field.getAnnotation(Index.class);
                if (null != index) {
                    if (index.isFullText()) {
                        indexString = ",FULLTEXT INDEX " + tableName + "_FULLTEXT_INDEX_" + fieldName + "(" + fieldName + ")";
                    } else {
                        indexString = ",INDEX " + tableName + "_INDEX_" + fieldName + "(" + fieldName + ")";
                    }
                }

                if (Objects.nonNull(field.getAnnotation(TableId.class))) {
                    columns.put(fieldName, "char(36) not null primary key" + indexString);
                    break;
                }

                Integer length = null;
                //检查是否有Column注释
                Column column = field.getAnnotation(Column.class);
                if (null != column) {
                    length = column.length();
                    if (length > 1024) {
                        columns.put(fieldName, "text null default null");
                        break;
                    }
                }

                Id id = field.getAnnotation(Id.class);
                if (null != id || fieldName.endsWith("_id")) {
                    if (null == length) {
                        columns.put(fieldName, "char(36) null default null" + indexString);
                        break;
                    }
                }

                if (null == length) {
                    length = 255;
                }

                columns.put(fieldName, "varchar(" + length + ") null default null" + indexString);
                break;
            case "Date":
            case "LocalDate":
                columns.put(fieldName, "date null default null");
                break;
            case "DateTime":
            case "LocalTime":
            case "Timestamp":
            case "LocalDateTime":
                columns.put(fieldName, "datetime(3) null default null");
                break;
            case "Boolean":
                columns.put(fieldName, "bit(1) null default null");
                break;
            case "Integer":
                columns.put(fieldName, "int(11) null default null");
                break;
            case "Long":
                columns.put(fieldName, "bigint(20) null default null");
                break;
            case "BigDecimal":
                columns.put(fieldName, "decimal(19,2) null default null");
                break;
            default:
                // 枚举
                EnumString enumString = field.getAnnotation(EnumString.class);
                if (null != enumString && enumString.isString()) {
                    columns.put(fieldName, "varchar(" + enumString.length() + ") null default null");
                } else {
                    columns.put(fieldName, "int(2) null default null");
                }

                break;
        }
        return fieldName;
    }

    /**
     * @param clazz: 对应PO
     * @return 当前类以及所有父类中的field
     * @Description: 获取当前类以及所有父类中的field
     * @Author: WYF
     * @Date: 2020/12/9 14:49
     */
    private List<Field> getClassFields(Class<?> clazz) {
        if (Objects.isNull(clazz)) {
            return new ArrayList<>();
        }
        List<Field> fieldArray = new ArrayList<>();
        if (Objects.isNull(clazz.getSuperclass())) {
            fieldArray.addAll(Arrays.asList(clazz.getDeclaredFields()));
            return fieldArray;
        }
        fieldArray.addAll(getClassFields(clazz.getSuperclass()));
        fieldArray.addAll(Arrays.asList(clazz.getDeclaredFields()));
        return fieldArray;
    }


}
